package com.lipaluma.parser;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lipaluma.context.Context;
import com.lipaluma.context.mapping.impl.ColumnMappingManager;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.UnmarshalFieldMapper;
import com.lipaluma.io.Row;
import com.lipaluma.util.ReflectionUtils;

public class AbstractUnmarshallerDataParser<TARGET> {
	private static Logger LOG = LoggerFactory.getLogger(AbstractDataParser.class);

	protected Class<TARGET> targetClass;
	protected List<FieldMapper> fieldMappers;
	protected Context context;
	
	private AbstractUnmarshallerDataParser(Builder<? extends DataParser<TARGET>, TARGET> builder) {
		this.targetClass = builder.targetClass;
		this.context = builder.context;
		this.fieldMappers = new ArrayList<FieldMapper>(builder.mappers);
	}
	
	/**
	 * Process a Row of the entry file and returns the object mapped corresponding.
	 * 
	 * @param row a line of the entry file
	 * @return the object mapped
	 */
	protected TARGET unmarshal(Row row) {
		try {
			TARGET target = targetClass.newInstance();
			for (FieldMapper mapper : fieldMappers) {
				if(!UnmarshalFieldMapper.class.isAssignableFrom(mapper.getClass()))
					throw new CsvParserException(mapper.getDescription()+" : the column must be a UnmarshalFieldMapper to parse a row to an object");
				processFieldMapper(target, mapper, row);
			}
			return target;
		} catch (InstantiationException ex) {
			throw new CsvParserException("The Pojo object can't be instanciated.", ex);
		} catch (IllegalAccessException ex) {
			throw new CsvParserException("The Pojo object can't be instanciated.", ex);
		}
	}

	protected void processFieldMapper(TARGET target, FieldMapper fieldMapper , Row row) {
		Object value = fieldMapper.unmarshal(row);
		injectValueToTarget(value, target, fieldMapper.getField());
	}
	
	protected void injectValueToTarget(Object value, TARGET target, Field field) {
		ReflectionUtils.makeAccessible(field);
		if(Collection.class.isAssignableFrom(field.getType())) {
			Collection<Object> collection = (Collection<Object>)ReflectionUtils.getField(field, target);
			if(collection == null) {
				collection = ReflectionUtils.instantiateCollection(field.getType());
				ReflectionUtils.setField(field, target, collection);
			}
			collection.addAll((Collection<Object>)value);
		} else {
			ReflectionUtils.setField(field, target, value);
		}
	}

	public static abstract class Builder<PARSER extends AbstractUnmarshallerDataParser<TARGET>, TARGET> {
		protected Class<TARGET> targetClass;
		protected List<FieldMapper> mappers = new ArrayList<FieldMapper>();
		protected Context context = new Context(new ColumnMappingManager());
		
		protected Builder(Class<TARGET> targetClass) {
			this.targetClass = targetClass;
		}
		
		public Builder<PARSER, TARGET> withContext(Context context) {
			this.context = context;
			return this;
		}

		public Builder<PARSER, TARGET> withFieldMappers(List<FieldMapper> mappers) {
			this.mappers.addAll(mappers);
			return this;
		}
		
		public abstract PARSER build();
	}

}
